package edu.fiu.cis.selfhealingdisk;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * @author vanvorst
 * 
 */
public class Configuration {
	public enum MetaDataLocationEnum {
		SAME_UNIT("same_unit"), NEXT_BLOCK("next_block"), PARITY_UNIT(
				"parity_unit");

		public final String s;

		private MetaDataLocationEnum(String s) {
			this.s = s;
		}

		public static MetaDataLocationEnum getEnum(String s) {
			for (MetaDataLocationEnum t : values())
				if (t.s.equals(s))
					return t;
			throw new RuntimeException("Unknown meta data location type " + s);
		}
	}

	public enum ParityLayoutEnum {
		MOD("mod"), ;
		public final String s;

		private ParityLayoutEnum(String s) {
			this.s = s;
		}

		public static ParityLayoutEnum getEnum(String s) {
			for (ParityLayoutEnum t : values())
				if (t.s.equals(s))
					return t;
			throw new RuntimeException("Unknown parity data location type " + s);
		}
	}

	public enum FlushIntervalEnum {
		IMMEDIATE("immediate"), BATCH("batch"), ;
		public final String s;

		private FlushIntervalEnum(String s) {
			this.s = s;
		}

		public static FlushIntervalEnum getEnum(String s) {
			for (FlushIntervalEnum t : values())
				if (t.s.equals(s))
					return t;
			throw new RuntimeException("Unknown flush interval type " + s);
		}
	}

	public enum ParityFormatEnum {
		FRAGMENTED("fragmented"), LOG("log"), NOPARITY("no_parity"), ;
		public final String s;

		private ParityFormatEnum(String s) {
			this.s = s;
		}

		public static ParityFormatEnum getEnum(String s) {
			for (ParityFormatEnum t : values())
				if (t.s.equals(s))
					return t;
			throw new RuntimeException("Unknown parity format type " + s);
		}
	}

	// DiskSim Files
	private String diskSimParFile = null;
	private String diskSimOutFile = null;

	// Data Model Parameters
	private int dataUnitSize = -1;
	private boolean identityTag = true;
	private boolean checksum = true;
	private boolean timestamp = true;
	private MetaDataLocationEnum location = MetaDataLocationEnum.SAME_UNIT;

	// Parity Model Parameters
	private int parityGroupSize = -1;
	private ParityLayoutEnum parityLayout = ParityLayoutEnum.MOD;
	private int modIndex = -1;
	private int modParityLocation = -1;
	private FlushIntervalEnum flushInterval = FlushIntervalEnum.IMMEDIATE;
	private int batchMaxUpdates = -1;
	private int batchMaxTime = -1;
	private ParityFormatEnum parityFormat = ParityFormatEnum.NOPARITY;

	// Memory Management
	private boolean memoryManagement = true;
	private int memorySizeInBlocks = 1024;
	private long maxTimeInMemory = 2000000;
	private double highWaterMark = 0.8;
	private double lowWaterMark = 0.6;

	// Fault Model Parameters
	private int probabilityOfLatentSectorErrors = 0;
	private int probabilityOfDataCorruption = 0;
	private int probabilityOfTornWrites = 0;
	private int latentSectorErrorMeanSize = 0;
	private float latentSectorErrorSizeVariance = 0;
	private int dataCorruptionMeanSize = 8;
	private float dataCorruptionSizeVariance = 1;

	private enum StringsEnum {
		SelfHealingDisk_tok("SelfHealingDisk"), DataModel_tok("DataModel"), ParityModel_tok(
				"ParityModel"), FaultModel_tok("FaultModel"), LBrace("{"), RBrace(
				"}"), Assign("="), dataUnitSize_tok("dataUnitSize"), identityTag_tok(
				"identityTag"), checksum_tok("checksum"), timestamp_tok(
				"timestamp"), location_tok("location"), parityGroupSize_tok(
				"parityGroupSize"), parityLayout_tok("parityLayout"), modIndex_tok(
				"modIndex"), modParityLocation_tok("modParityLocation"), flushInterval_tok(
				"flushInterval"), batchMaxUpdates_tok("batchMaxUpdates"), batchMaxTime_tok(
				"batchMaxTime"), parityFormat_tok("parityFormat"), parityMem_tok(
				"ReserveMemoryForParity"), memReserved_tok("MemorySize"), maxtimeme_tok(
				"MaxTimeInMemory"), hgwm_tok("HighWatermark"), lwwm_tok(
				"LowWatermark"), probabilityOfLatentSectorErrors_tok(
				"probabilityOfLatentSectorErrors"), probabilityOfDataCorruption_tok(
				"probabilityOfDataCorruption"), probabilityOfTornWrites_tok(
				"probabilityOfTornWrites"), latentSectorErrorMeanSize_tok(
				"latentSectorErrorMeanSize"), latentSectorErrorSizeVariance_tok(
				"latentSectorErrorSizeVariance"), dataCorruptionMeanSize_tok(
				"dataCorruptionMeanSize"), dataCorruptionSizeVariance_tok(
				"dataCorruptionSizeVariance"), disksimParFile_tok(
				"DiskSimParameterFile"), disksimOutFile_tok(
				"DiskSimOutputFile"),disksim_tok("DiskSim");
		public final String val;

		private StringsEnum(String val) {
			this.val = val;
		}

		public boolean equals(String str) {
			if (val.length() != str.length())
				return false;
			// System.out.println("\t\t'"+val+"'=='"+str+"'-->"+(val.compareTo(str)));
			return 0 == val.compareTo(str);
		}

		public static StringsEnum getEnum(String str) {
			for (StringsEnum e : values()) {
				if (e.equals(str))
					return e;
			}
			return null;
		}
	}

	/**
	 * @param file
	 * @throws IOException
	 */
	public Configuration(final String file) throws IOException {
		final List<String> toks = new ArrayList<String>();
		final BufferedReader br = new BufferedReader(new InputStreamReader(
				new DataInputStream(new FileInputStream(file))));
		String str = null;
		while ((str = br.readLine()) != null) {
			for (String t : str.trim().split("[\\s={}]")) {
				if (!(StringsEnum.SelfHealingDisk_tok.equals(t)
						|| StringsEnum.DataModel_tok.equals(t)
						|| StringsEnum.ParityModel_tok.equals(t)
						|| StringsEnum.disksim_tok.equals(t)
						|| StringsEnum.FaultModel_tok.equals(t)
						|| StringsEnum.LBrace.equals(t)
						|| StringsEnum.RBrace.equals(t)
						|| StringsEnum.Assign.equals(t) || t.length() == 0)) {
					toks.add(t);
				}
			}
		}
		br.close();
		int index = 0;
		while (index < toks.size()) {
			
			StringsEnum prop = StringsEnum.getEnum(toks.get(index));
			String val = toks.get(index + 1);
			if (prop == null || val == null)
				throw new RuntimeException("Configuration error near '"
						+ toks.get(index) + "'");
			index += 2;
			
			switch (prop) {
			case dataUnitSize_tok:
				this.setDataUnitSize(Integer.parseInt(val));
				break;
			case identityTag_tok:
				this.setIdentityTag(Boolean.parseBoolean(val));
				break;
			case checksum_tok:
				this.setChecksum(Boolean.parseBoolean(val));
				break;
			case timestamp_tok:
				this.setTimestamp(Boolean.parseBoolean(val));
				break;
			case location_tok:
				this.setMetaDataLocation(MetaDataLocationEnum.getEnum(val));
				break;
			case parityGroupSize_tok:
				this.setParityGroupSize(Integer.parseInt(val));
				break;
			case parityLayout_tok:
				this.setParityLayout(ParityLayoutEnum.getEnum(val));
				break;
			case modIndex_tok:
				this.setModIndex(Integer.parseInt(val));
				break;
			case modParityLocation_tok:
				this.setModParityLocation(Integer.parseInt(val));
				break;
			case flushInterval_tok:
				this.setFlushInterval(FlushIntervalEnum.getEnum(val));
				break;
			case batchMaxUpdates_tok:
				this.setBatchMaxUpdates(Integer.parseInt(val));
				break;
			case batchMaxTime_tok:
				this.setBatchMaxTime(Integer.parseInt(val));
				break;
			case parityFormat_tok:
				this.setParityFormat(ParityFormatEnum.getEnum(val));
				break;
			case parityMem_tok:
				this.setMemoryManagement(Boolean.parseBoolean(val));
				break;
			case memReserved_tok:
				this.setMemorySizeInBlocks(Integer.parseInt(val));
				break;
			case maxtimeme_tok:
				this.setMaxTimeInMemory((long) (Long.parseLong(val) * 1E6));
				break;
			case hgwm_tok:
				this.setHighWaterMark(Double.parseDouble(val));
				break;
			case lwwm_tok:
				this.setLowWaterMark(Double.parseDouble(val));
				break;
			case probabilityOfLatentSectorErrors_tok:
				this.setProbabilityOfLatentSectorErrors(Integer.parseInt(val));
				break;
			case probabilityOfDataCorruption_tok:
				this.setProbabilityOfDataCorruption(Integer.parseInt(val));
				break;
			case probabilityOfTornWrites_tok:
				this.setProbabilityOfTornWrites(Integer.parseInt(val));
				break;
			case latentSectorErrorMeanSize_tok:
				this.setLatentSectorErrorMeanSize(Integer.parseInt(val));
				break;
			case latentSectorErrorSizeVariance_tok:
				this.setLatentSectorErrorSizeVariance(Float.parseFloat(val));
				break;
			case dataCorruptionMeanSize_tok:
				this.setDataCorruptionMeanSize(Integer.parseInt(val));
				break;
			case dataCorruptionSizeVariance_tok:
				this.setDataCorruptionSizeVariance(Float.parseFloat(val));
				break;
			case disksimParFile_tok:
				this.setDiskSimParFile(val);
				break;
			case disksimOutFile_tok:
				this.setDiskSimOutFile(val);
				break;
			default:
				throw new RuntimeException("Configuration error near '"
						+ toks.get(index) + "'");
			}
		}

		if (this.diskSimOutFile == null || this.diskSimParFile == null)
			throw new RuntimeException(
					"You must assign a parameter and output file for DiskSim");

	}

	/**
	 * @return the dataUnitSize
	 */
	public final int getDataUnitSize() {
		return dataUnitSize;
	}

	/**
	 * @param dataUnitSize
	 *            the dataUnitSize to set
	 */
	public final void setDataUnitSize(int dataUnitSize) {
		this.dataUnitSize = dataUnitSize;
	}

	/**
	 * @return the identityTag
	 */
	public final boolean isIdentityTag() {
		return identityTag;
	}

	/**
	 * @param identityTag
	 *            the identityTag to set
	 */
	public final void setIdentityTag(boolean identityTag) {
		this.identityTag = identityTag;
	}

	/**
	 * @return the checksum
	 */
	public final boolean isChecksum() {
		return checksum;
	}

	/**
	 * @param checksum
	 *            the checksum to set
	 */
	public final void setChecksum(boolean checksum) {
		this.checksum = checksum;
	}

	/**
	 * @return the timestamp
	 */
	public final boolean isTimestamp() {
		return timestamp;
	}

	/**
	 * @param timestamp
	 *            the timestamp to set
	 */
	public final void setTimestamp(boolean timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * @return the location
	 */
	public final MetaDataLocationEnum getMetaDataLocation() {
		return location;
	}

	/**
	 * @param location
	 *            the location to set
	 */
	public final void setMetaDataLocation(MetaDataLocationEnum location) {
		this.location = location;
	}

	/**
	 * @return the parityGroupSize
	 */
	public final int getParityGroupSize() {
		return parityGroupSize;
	}

	/**
	 * @param parityGroupSize
	 *            the parityGroupSize to set
	 */
	public final void setParityGroupSize(int parityGroupSize) {
		this.parityGroupSize = parityGroupSize;
	}

	/**
	 * @return the paritylayout
	 */
	public final ParityLayoutEnum getParityLayout() {
		return parityLayout;
	}

	/**
	 * @param paritylayout
	 *            the paritylayout to set
	 */
	public final void setParityLayout(ParityLayoutEnum paritylayout) {
		this.parityLayout = paritylayout;
	}

	/**
	 * @return the modIndex
	 */
	public final int getModIndex() {
		return modIndex;
	}

	/**
	 * @param modIndex
	 *            the modIndex to set
	 */
	public final void setModIndex(int modIndex) {
		this.modIndex = modIndex;
	}

	/**
	 * @return the modParityLocation
	 */
	public final int getModParityLocation() {
		return modParityLocation;
	}

	/**
	 * @param modParityLocation
	 *            the modParityLocation to set
	 */
	public final void setModParityLocation(int modParityLocation) {
		this.modParityLocation = modParityLocation;
	}

	/**
	 * @return the flushInterval
	 */
	public final FlushIntervalEnum getFlushInterval() {
		return flushInterval;
	}

	/**
	 * @param flushInterval
	 *            the flushInterval to set
	 */
	public final void setFlushInterval(FlushIntervalEnum flushInterval) {
		this.flushInterval = flushInterval;
	}

	/**
	 * @return the batchMaxUpdates
	 */
	public final int getBatchMaxUpdates() {
		return batchMaxUpdates;
	}

	/**
	 * @param batchMaxUpdates
	 *            the batchMaxUpdates to set
	 */
	public final void setBatchMaxUpdates(int batchMaxUpdates) {
		this.batchMaxUpdates = batchMaxUpdates;
	}

	/**
	 * @return the batchMaxTime
	 */
	public final int getBatchMaxTime() {
		return batchMaxTime;
	}

	/**
	 * @param batchMaxTime
	 *            the batchMaxTime to set
	 */
	public final void setBatchMaxTime(int batchMaxTime) {
		this.batchMaxTime = batchMaxTime;
	}

	/**
	 * @return the parityFormat
	 */
	public final ParityFormatEnum getParityFormat() {
		return parityFormat;
	}

	/**
	 * @param parityFormat
	 *            the parityFormat to set
	 */
	public final void setParityFormat(ParityFormatEnum parityformat) {
		this.parityFormat = parityformat;
	}

	/**
	 * @return the probabilityOfLatentSectorErrors
	 */
	public final int getProbabilityOfLatentSectorErrors() {
		return probabilityOfLatentSectorErrors;
	}

	/**
	 * @param probabilityOfLatentSectorErrors
	 *            the probabilityOfLatentSectorErrors to set
	 */
	public final void setProbabilityOfLatentSectorErrors(
			int probabilityOfLatentSectorErrors) {
		this.probabilityOfLatentSectorErrors = probabilityOfLatentSectorErrors;
	}

	/**
	 * @return the probabilityOfDataCorruption
	 */
	public final int getProbabilityOfDataCorruption() {
		return probabilityOfDataCorruption;
	}

	/**
	 * @param probabilityOfDataCorruption
	 *            the probabilityOfDataCorruption to set
	 */
	public final void setProbabilityOfDataCorruption(
			int probabilityOfDataCorruption) {
		this.probabilityOfDataCorruption = probabilityOfDataCorruption;
	}

	/**
	 * @return the probabilityOfTornWrites
	 */
	public final int getProbabilityOfTornWrites() {
		return probabilityOfTornWrites;
	}

	/**
	 * @param probabilityOfTornWrites
	 *            the probabilityOfTornWrites to set
	 */
	public final void setProbabilityOfTornWrites(int probabilityOfTornWrites) {
		this.probabilityOfTornWrites = probabilityOfTornWrites;
	}

	/**
	 * @return the latentSectorErrorMeanSize
	 */
	public final int getLatentSectorErrorMeanSize() {
		return latentSectorErrorMeanSize;
	}

	/**
	 * @param latentSectorErrorMeanSize
	 *            the latentSectorErrorMeanSize to set
	 */
	public final void setLatentSectorErrorMeanSize(int latentSectorErrorMeanSize) {
		this.latentSectorErrorMeanSize = latentSectorErrorMeanSize;
	}

	/**
	 * @return the latentSectorErrorSizeVariance
	 */
	public final float getLatentSectorErrorSizeVariance() {
		return latentSectorErrorSizeVariance;
	}

	/**
	 * @param latentSectorErrorSizeVariance
	 *            the latentSectorErrorSizeVariance to set
	 */
	public final void setLatentSectorErrorSizeVariance(
			float latentSectorErrorSizeVariance) {
		this.latentSectorErrorSizeVariance = latentSectorErrorSizeVariance;
	}

	/**
	 * @return the dataCorruptionMeanSize
	 */
	public final int getDataCorruptionMeanSize() {
		return dataCorruptionMeanSize;
	}

	/**
	 * @param dataCorruptionMeanSize
	 *            the dataCorruptionMeanSize to set
	 */
	public final void setDataCorruptionMeanSize(int dataCorruptionMeanSize) {
		this.dataCorruptionMeanSize = dataCorruptionMeanSize;
	}

	/**
	 * @return the dataCorruptionSizeVariance
	 */
	public final float getDataCorruptionSizeVariance() {
		return dataCorruptionSizeVariance;
	}

	/**
	 * @param dataCorruptionSizeVariance
	 *            the dataCorruptionSizeVariance to set
	 */
	public final void setDataCorruptionSizeVariance(
			float dataCorruptionSizeVariance) {
		this.dataCorruptionSizeVariance = dataCorruptionSizeVariance;
	}

	public boolean getMemoryManagement() {
		return memoryManagement;
	}

	public void setMemoryManagement(boolean memoryManagement) {
		this.memoryManagement = memoryManagement;
	}

	public MetaDataLocationEnum getLocation() {
		return location;
	}

	public void setLocation(MetaDataLocationEnum location) {
		this.location = location;
	}

	public int getMemorySizeInBlocks() {
		return memorySizeInBlocks;
	}

	public void setMemorySizeInBlocks(int memorySizeInBlocks) {
		this.memorySizeInBlocks = memorySizeInBlocks;
	}

	public long getMaxTimeInMemory() {
		return maxTimeInMemory;
	}

	public void setMaxTimeInMemory(long maxTimeInMemory) {
		this.maxTimeInMemory = maxTimeInMemory;
	}

	public double getHighWaterMark() {
		return highWaterMark;
	}

	public void setHighWaterMark(double highWaterMark) {
		this.highWaterMark = highWaterMark;
	}

	public double getLowWaterMark() {
		return lowWaterMark;
	}

	public void setLowWaterMark(double lowWaterMark) {
		this.lowWaterMark = lowWaterMark;
	}

	public String getDiskSimOutFile() {
		return diskSimOutFile;
	}

	public void setDiskSimOutFile(String diskSimOutFile) {
		this.diskSimOutFile = diskSimOutFile;
	}

	public String getDiskSimParFile() {
		return diskSimParFile;
	}

	public void setDiskSimParFile(String diskSimParFile) {
		this.diskSimParFile = diskSimParFile;
	}
}
